package com.geqian.document4j.pdf;

import com.geqian.document4j.common.annotation.TableField;
import com.geqian.document4j.common.core.BaseTableInterceptor;
import com.geqian.document4j.common.extend.Converter;
import com.geqian.document4j.common.extend.NoConverter;
import com.geqian.document4j.common.core.WriteTableable;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <dependency>
 * <groupId>com.itextpdf</groupId>
 * <artifactId>itextpdf</artifactId>
 * <version>5.2.0</version>
 * </dependency>
 *
 * <dependency>
 * <groupId>com.itextpdf</groupId>
 * <artifactId>itext-asian</artifactId>
 * <version>5.2.0</version>
 * </dependency>
 *
 * @author geqian
 * @date 15:46 2024/1/16
 */
public class PDFBuilder {

    private final Logger log = LoggerFactory.getLogger(PDFBuilder.class);

    private Document document;

    private PdfWriter pdfWriter;

    private OutputStream outputStream;

    private Map<String, String> enumMappings;

    private Stack<Map.Entry<Integer, PdfOutline>> stack;

    private Map<String, Converter> converters;

    private PDFBuilder(OutputStream outputStream) {
        try {
            this.document = new Document(PageSize.A4);
            float padding = PageSize.A4.getWidth() * 0.1f;
            // 设置左右填充为页面宽度的 10%
            document.setMargins(padding, padding, (float) (0.8 * padding), (float) (0.8 * padding));
            this.outputStream = outputStream;
            pdfWriter = PdfWriter.getInstance(this.document, this.outputStream);
            this.stack = new Stack<>();
            //if (watermark != null && !watermark.trim().isEmpty()) {
            //    pdfWriter.setPageEvent(new WaterMarkEvent(createFont(), watermark));
            //}
            this.document.open();
            // 获取根大纲
            PdfOutline root = pdfWriter.getRootOutline();
            this.stack.push(new AbstractMap.SimpleEntry<>(0, root));
        } catch (Exception e) {
            throw new RuntimeException("创建PdfBuilder异常", e);
        }
    }

    private PDFBuilder() {
        new PDFBuilder(new ByteArrayOutputStream());
    }


    public static PDFBuilder create() {
        return new PDFBuilder();
    }


    public static PDFBuilder create(OutputStream target) {
        return new PDFBuilder(target);
    }


    /**
     * 设置文档说明（描述）信息
     *
     * @param des
     * @return
     */
    public PDFBuilder description(Description des) {
        String title = des.getTitle();
        String author = des.getAuthor();
        String subject = des.getSubject();
        String keywords = des.getKeywords();
        String creator = des.getCreator();
        if (isNotBlank(title)) {
            this.document.addTitle(title);
        }
        if (isNotBlank(author)) {
            this.document.addAuthor(author);
        }
        if (isNotBlank(subject)) {
            this.document.addSubject(subject);
        }
        if (isNotBlank(keywords)) {
            this.document.addKeywords(keywords);
        }
        if (isNotBlank(creator)) {
            this.document.addCreator(creator);
        }
        if (des.isCreateTime()) {
            this.document.addCreationDate();
        }
        if (des.isProducer()) {
            this.document.addProducer();
        }
        return this;
    }


    /**
     * 添加标题（书签）
     *
     * @param title
     * @param level
     * @param styles
     * @return
     * @throws DocumentException
     */
    public PDFBuilder title(String title, PDFStyle.Title level, PDFStyle... styles) {
        if (level == null) {
            throw new IllegalArgumentException("Title level cannot be null");
        }
        if (styles != null) {
            // 创建一个新数组，长度比原数组长1
            PDFStyle[] newArray = new PDFStyle[styles.length + 1];
            // 将新元素放到新数组的第一个位置
            newArray[0] = level;
            // 将原数组的元素复制到新数组中，从新数组的第二个位置开始
            System.arraycopy(styles, 0, newArray, 1, styles.length);
            styles = newArray;
        } else {
            styles = new PDFStyle[]{level};
        }
        // 锚点
        String anchor = UUID.randomUUID().toString();
        Chunk anchorChunk = new Chunk(title, createFont(styles));
        anchorChunk.setLocalDestination(anchor);
        try {
            document.add(new Paragraph(anchorChunk));
            // 添加空行分隔内容
            blankRow();
            // 使用锚点名
            PdfAction action = PdfAction.gotoLocalPage(anchor, true);
            while (!stack.isEmpty() && stack.peek().getKey() >= level.getLevel()) {
                stack.pop();
            }
            PdfOutline parent = stack.peek().getValue();
            PdfOutline pdfOutline = new PdfOutline(parent, action, title);
            stack.push(new AbstractMap.SimpleEntry<>(level.getLevel(), pdfOutline));
        } catch (DocumentException e) {
            log.error("添加标题时发生异常", e);
            return this;
        }
        return this;
    }

    /**
     * 空白行
     *
     * @return
     */
    public PDFBuilder blankRow() {
        try {
            Paragraph paragraph = new Paragraph();
            paragraph.add(Chunk.NEWLINE);
            this.document.add(paragraph);
        } catch (DocumentException e) {
            log.error("添加空白行发生异常", e);
        }
        return this;
    }


    /**
     * 添加一个新页面
     *
     * @return
     */
    public PDFBuilder newPage() {
        this.document.newPage();
        return this;
    }


    /**
     * 添加列表
     *
     * @param elements
     * @param pdfStyles
     * @return
     */
    public PDFBuilder list(List<String> elements, PDFStyle... pdfStyles) {
        try {
            com.itextpdf.text.List list = new com.itextpdf.text.List();
            for (String element : elements) {
                list.add(createParagraph(element, pdfStyles));
            }
            this.document.add(list);
        } catch (DocumentException e) {
            log.error("添加列表发生异常", e);
        }
        return this;
    }


    /**
     * 添加段落
     *
     * @param text
     * @param pdfStyles
     * @return
     */
    public PDFBuilder paragraph(String text, PDFStyle... pdfStyles) {
        try {
            Paragraph paragraph = createParagraph(text, pdfStyles);
            this.document.add(paragraph);
        } catch (DocumentException e) {
            log.error("添加段落异常", e);
        }
        return this;
    }


    /**
     * 添加图片
     *
     * @param bytes
     * @return
     */
    public PDFBuilder picture(byte[] bytes, int width, int height) {
        try {
            // 读取图片文件
            Image image = Image.getInstance(bytes);
            // 调整图片大小（可选）
            image.scaleToFit(width, height);
            this.document.add(image);
        } catch (Exception e) {
            log.error("添加图片发生异常", e);
            return this;
        }
        return this;
    }


    /**
     * 添加表格
     *
     * @param dataList 数据列表
     */
    public PDFBuilder table(List<? extends WriteTableable> dataList, PDFStyle... pdfStyles) {

        if (dataList != null && !(dataList = dataList.stream().filter(Objects::nonNull).collect(Collectors.toList())).isEmpty()) {

            WriteTableable writeTableable = dataList.get(0);

            //获取需要输出字段
            List<Field> fields = getFieldList(writeTableable);

            if (writeTableable instanceof BaseTableInterceptor) {
                fields = ((BaseTableInterceptor) writeTableable).interceptFields(writeTableable.getClass(), fields);
            }

            int colums = fields.size();

            PdfPTable table = createTable(colums);

            if (writeTableable instanceof PDFTableInterceptor) {
                ((PDFTableInterceptor) writeTableable).interceptPdfTable(table);
            }

            //加载枚举映射关系
            loadEnumMappings(fields);

            //加载全部属性值转换器
            loadConverters(fields);

            //获取表头
            List<String> tableHeaders = getTableHeaders(fields);

            if (writeTableable instanceof BaseTableInterceptor) {
                tableHeaders = ((BaseTableInterceptor) writeTableable).interceptTableHeaders(tableHeaders, writeTableable.getClass());
            }

            //写入表头
            writeTableHeaders(table, tableHeaders, writeTableable, pdfStyles);

            //写入表格主体数据
            writeTableRows(table, dataList, fields, pdfStyles);

            try {
                this.document.add(table);
            } catch (DocumentException e) {
                log.error("添加表格发生异常", e);
                return this;
            }
        }
        return this;
    }


    /**
     * 数据写入表格
     *
     * @param rows 数据列表
     */
    public PDFBuilder table(Object[][] rows, PDFStyle... pdfStyles) throws Exception {
        int row;
        int column;
        if (rows != null && (rows = Stream.of(rows).filter(Objects::nonNull).toArray(Object[][]::new)).length > 0) {
            //有效行数
            row = rows.length;
            Optional<Object[]> optional = Stream.of(rows).max(Comparator.comparingInt(array -> array == null ? 0 : array.length));
            if (optional.isPresent()) {
                //子数组长度最大的长度作为列数
                column = optional.get().length;
                //创建表格
                PdfPTable table = createTable(column);
                for (int i = 0; i < row; i++) {
                    for (int j = 0; j < rows[i].length; j++) {
                        String cellValue = String.valueOf(rows[i][j]);
                        PdfPCell cell = createCell(cellValue, pdfStyles);
                        table.addCell(cell);
                    }
                }
                this.document.add(table);
            }
        }
        return this;
    }


    /**
     * 段落模式
     *
     * @return
     */
    public ParagraphBuilder startParagraph() {
        return new ParagraphBuilder();
    }


    /**
     * 构建PDF文档
     */
    public void build() {
        closeResources(this.document, this.pdfWriter);
    }


    /**
     * PDF文档转换为字节数组
     *
     * @return
     */
    public byte[] asBytes() {
        try {
            return ((ByteArrayOutputStream) outputStream).toByteArray();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * PDF文档写入文件
     *
     * @return
     */
    public void asFile(File file) {
        try (OutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(file.toPath()))) {
            outputStream.write(asBytes());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * PDF 文档写入文件
     *
     * @return
     */
    public void asFile(String filePath) {
        asFile(new File(filePath));
    }


    /**
     * PDF文档写入到输出流
     *
     * @return
     */
    public void asOutputStream(OutputStream outputStream) {
        try {
            outputStream.write(asBytes());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 加载全部属性值转换器
     *
     * @param fields
     */
    private void loadConverters(List<Field> fields) {
        this.converters = new HashMap<>();
        for (Field field : fields) {
            if (field.isAnnotationPresent(TableField.class)) {
                TableField annotation = field.getAnnotation(TableField.class);
                if (annotation.converter() != NoConverter.class) {
                    try {
                        Converter converter = annotation.converter().getConstructor().newInstance();
                        this.converters.put(getFieldKey(field), converter);
                    } catch (Exception e) {
                        log.error("创建 Converter 转换器发生异常", e);
                    }
                }
            }
        }
    }


    /**
     * 加载全部枚举映射关系
     *
     * @param fields
     * @return
     */
    private void loadEnumMappings(List<Field> fields) {
        this.enumMappings = new HashMap<>();
        for (Field field : fields) {
            if (field.isAnnotationPresent(TableField.class)) {
                TableField annotation = field.getAnnotation(TableField.class);
                String[] enums = annotation.enums();
                if (enums.length > 0) {
                    appendEnumMapping(getFieldKey(field), enums);
                }
            }
        }
    }


    /**
     * 追加一组枚举映射关系
     *
     * @param fieldKey
     * @param enums
     */
    private void appendEnumMapping(String fieldKey, String[] enums) {
        for (String enumStr : enums) {
            String[] splitArray = enumStr.split("->");
            if (splitArray.length != 2) {
                throw new IllegalArgumentException("The convert property format of TableField is：oldValue -> newValue");
            }
            this.enumMappings.put(fieldKey + ":" + splitArray[0].trim(), splitArray[1].trim());
        }
    }


    /**
     * 创建表格
     *
     * @param columns
     * @return
     */
    private PdfPTable createTable(int columns) {
        //创建表格
        PdfPTable table = new PdfPTable(columns);
        //设置表格样式
        setTableStyle(table);

        return table;
    }


    /**
     * 写入表头
     *
     * @param table
     * @param tableHeaders
     * @param pdfStyles
     */
    private void writeTableHeaders(PdfPTable table, List<String> tableHeaders, WriteTableable obj, PDFStyle... pdfStyles) {

        for (String header : tableHeaders) {
            PdfPCell cell = createCell(header, pdfStyles);
            if (obj instanceof PDFTableInterceptor) {
                ((PDFTableInterceptor) obj).interceptPdfHeaderCell(cell, obj.getClass());
            }
            table.addCell(cell);
        }
    }

    /**
     * 写入表格多行数据
     *
     * @param table
     * @param dataList
     * @param fields
     * @param pdfStyles
     */
    private void writeTableRows(PdfPTable table, List<? extends WriteTableable> dataList, List<Field> fields, PDFStyle... pdfStyles) {
        for (WriteTableable writeTableable : dataList) {
            writeTableRow(table, writeTableable, fields, pdfStyles);
        }
    }


    /**
     * 写入表格一行数据
     *
     * @param table
     * @param obj
     * @param fieldList
     * @param pdfStyles
     */
    private void writeTableRow(PdfPTable table, WriteTableable obj, List<Field> fieldList, PDFStyle... pdfStyles) {
        for (Field field : fieldList) {
            String cellValue = getCellValue(obj, field);
            PdfPCell cell = createCell(cellValue, pdfStyles);
            //拦截单元格
            if (obj instanceof PDFTableInterceptor) {
                ((PDFTableInterceptor) obj).interceptPdfContentCell(cell, obj, field);
            }
            table.addCell(cell);
        }
    }

    /**
     * 获取单元格值
     *
     * @param obj
     * @param field
     * @return
     */
    private String getCellValue(WriteTableable obj, Field field) {
        Object fieldValue;
        try {
            field.setAccessible(true);
            fieldValue = field.get(obj);
            //执行属性值枚举映射
            Object enumValue = this.enumMappings.get(getFieldKey(field) + ":" + fieldValue);
            if (enumValue != null) {
                fieldValue = enumValue;
            }
            //执行属性值转换
            Converter converter = this.converters.get(getFieldKey(field));
            if (converter != null) {
                fieldValue = converter.convert(fieldValue);
            }
        } catch (IllegalAccessException e) {
            fieldValue = "";
        }
        return String.valueOf(fieldValue);
    }


    /**
     * 获取表头
     *
     * @param fieldList
     * @return
     */
    private List<String> getTableHeaders(List<Field> fieldList) {
        //通过注解获取需要输出的属性别名
        return fieldList.stream()
                .map(field -> {
                    TableField annotation = field.getAnnotation(TableField.class);
                    return annotation != null && !Objects.equals("", annotation.value())
                            ? annotation.value()
                            : field.getName();
                }).collect(Collectors.toList());
    }

    /**
     * 获取需要写入表格的属性
     *
     * @param obj
     * @return
     */
    private <T> List<Field> getFieldList(T obj) {

        List<Class<?>> classes = new ArrayList<>();

        Class<?> classType = obj.getClass();

        //遍历获取父类class
        while (!Objects.equals(classType, Object.class)) {
            classes.add(classType);
            classType = classType.getSuperclass();
        }

        // 获取子类及父类全部属性
        List<Field> fields = classes.stream()
                .map(pojoClass -> Stream.of(pojoClass.getDeclaredFields()).collect(Collectors.toList()))
                .flatMap(Collection::stream)
                .collect(Collectors.toList());

        // 属性中是否匹配到任意一个包含 TableField 注解
        boolean anyMatch = fields.stream()
                .anyMatch(field -> field.isAnnotationPresent(TableField.class));

        // 如果使用了 TableField 注解，只获取包含 TableField 注解的属性，反之获取全部属性
        if (anyMatch) {
            fields = fields.stream()
                    .filter(field -> field.isAnnotationPresent(TableField.class) && !field.getAnnotation(TableField.class).exclude())
                    .collect(Collectors.toList());
        }

        return fields.stream().sorted((a, b) -> {
            TableField before = a.getAnnotation(TableField.class);
            TableField next = b.getAnnotation(TableField.class);
            return (before == null ? Integer.MAX_VALUE : before.order()) - (next == null ? Integer.MAX_VALUE : next.order());
        }).collect(Collectors.toList());
    }


    /**
     * 设置表格样式
     *
     * @param table
     */
    private void setTableStyle(PdfPTable table) {
        // 设置表格上面空白宽度
        table.setSpacingBefore(10f);
        // 设置表格下面空白宽度
        table.setSpacingAfter(10f);
        // 设置表格的宽度占页面宽度的百分比
        table.setWidthPercentage(100);

    }

    /**
     * 获取类名+属性名
     *
     * @param field
     * @return
     */
    private String getFieldKey(Field field) {
        return field.getDeclaringClass().getName() + "." + field.getName();
    }


    /**
     * 关闭资源
     */
    private void closeResources(Document document, PdfWriter pdfWriter) {
        if (document != null) {
            document.close();
        }
        if (pdfWriter != null) {
            pdfWriter.close();
        }
    }


    /**
     * 关闭资源
     *
     * @param pdfWriter
     */
    private void closePdfWriter(PdfWriter pdfWriter) {
        if (pdfWriter != null) {
            pdfWriter.close();
        }
    }


    /**
     * 创建段落
     *
     * @param text
     * @return
     */
    private Paragraph createParagraph(String text, PDFStyle... pdfStyles) {
        Paragraph paragraph = new Paragraph(text, createFont(pdfStyles));
        if (pdfStyles != null) {
            for (PDFStyle pdfStyle : pdfStyles) {
                if (pdfStyle != null && PDFStyle.Paragraph.class.isAssignableFrom(pdfStyle.getClass())) {
                    pdfStyle.buildStyle(paragraph);
                }
            }
        }
        return paragraph;
    }


    /**
     * 创建字体
     *
     * @return
     */
    private Font createFont(PDFStyle... pdfStyles) {
        String fontFamily = null;
        Font font;
        try {
            if (pdfStyles != null) {
                for (PDFStyle pdfStyle : pdfStyles) {
                    if (pdfStyle instanceof PDFStyle.Font && (fontFamily = ((PDFStyle.Font) pdfStyle).getFamilyStyle()) != null) {
                        break;
                    }
                }
            }
            font = fontFamily != null ?
                    FontFactory.getFont(fontFamily, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED)
                    : FontFactory.getFont("fonts/simsun.ttc,0", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        } catch (Exception ignored) {
            font = FontFactory.getFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
        }
        font.setStyle(Font.NORMAL);
        font.setSize(Font.DEFAULTSIZE);
        if (pdfStyles != null) {
            for (PDFStyle pdfStyle : pdfStyles) {
                if (pdfStyle != null && (PDFStyle.Font.class.isAssignableFrom(pdfStyle.getClass()) || PDFStyle.Title.class.isAssignableFrom(pdfStyle.getClass()))) {
                    pdfStyle.buildStyle(font);
                }
            }
        }
        return font;
    }

    /**
     * 创建表格单元格
     *
     * @param text
     * @param pdfStyles
     * @return
     */
    private PdfPCell createCell(String text, PDFStyle... pdfStyles) {
        PdfPCell cell = new PdfPCell(new Phrase(text, createFont(pdfStyles)));
        // 设置单元格的水平和垂直对齐方式
        cell.setHorizontalAlignment(Element.ALIGN_LEFT);
        cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
        cell.setPaddingLeft(8);
        cell.setPaddingTop(5);
        cell.setPaddingBottom(5);
        cell.setBorderColor(BaseColor.GRAY);
        cell.setBorderWidth(0.5f);
        return cell;
    }

    private boolean isNotBlank(String text) {
        return text != null && !text.trim().isEmpty();
    }

    public class ParagraphBuilder {

        private final Paragraph paragraph;

        public ParagraphBuilder() {
            paragraph = new Paragraph();
        }

        public ParagraphBuilder text(String text, PDFStyle... pdfStyles) {
            Chunk chunk = new Chunk(text, createFont(pdfStyles));
            paragraph.add(chunk);
            if (pdfStyles != null) {
                for (PDFStyle pdfStyle : pdfStyles) {
                    if (pdfStyle != null && PDFStyle.Paragraph.class.isAssignableFrom(pdfStyle.getClass())) {
                        pdfStyle.buildStyle(paragraph);
                    }
                }
            }
            return this;
        }


        public PDFBuilder endParagraph() {
            try {
                PDFBuilder.this.document.add(paragraph);
                return PDFBuilder.this;
            } catch (DocumentException e) {
                throw new RuntimeException("写入文本发生异常", e);
            }
        }
    }

}
